home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / share / python-support / python-xdg / xdg / IconTheme.py < prev    next >
Encoding:
Python Source  |  2009-03-04  |  11.5 KB  |  389 lines

  1. """
  2. Complete implementation of the XDG Icon Spec Version 0.8
  3. http://standards.freedesktop.org/icon-theme-spec/
  4. """
  5.  
  6. import os, sys, time
  7.  
  8. from xdg.IniFile import *
  9. from xdg.BaseDirectory import *
  10. from xdg.Exceptions import *
  11.  
  12. import xdg.Config
  13.  
  14. class IconTheme(IniFile):
  15.     "Class to parse and validate IconThemes"
  16.     def __init__(self):
  17.         IniFile.__init__(self)
  18.  
  19.     def __repr__(self):
  20.         return self.name
  21.  
  22.     def parse(self, file):
  23.         IniFile.parse(self, file, ["Icon Theme", "KDE Icon Theme"])
  24.         self.dir = os.path.dirname(file)
  25.         (nil, self.name) = os.path.split(self.dir)
  26.  
  27.     def getDir(self):
  28.         return self.dir
  29.  
  30.     # Standard Keys
  31.     def getName(self):
  32.         return self.get('Name', locale=True)
  33.     def getComment(self):
  34.         return self.get('Comment', locale=True)
  35.     def getInherits(self):
  36.         return self.get('Inherits', list=True)
  37.     def getDirectories(self):
  38.         return self.get('Directories', list=True)
  39.     def getHidden(self):
  40.         return self.get('Hidden', type="boolean")
  41.     def getExample(self):
  42.         return self.get('Example')
  43.  
  44.     # Per Directory Keys
  45.     def getSize(self, directory):
  46.         return self.get('Size', type="integer", group=directory)
  47.     def getContext(self, directory):
  48.         return self.get('Context', group=directory)
  49.     def getType(self, directory):
  50.         value = self.get('Type', group=directory)
  51.         if value:
  52.             return value
  53.         else:
  54.             return "Threshold"
  55.     def getMaxSize(self, directory):
  56.         value = self.get('MaxSize', type="integer", group=directory)
  57.         if value or value == 0:
  58.             return value
  59.         else:
  60.             return self.getSize(directory)
  61.     def getMinSize(self, directory):
  62.         value = self.get('MinSize', type="integer", group=directory)
  63.         if value or value == 0:
  64.             return value
  65.         else:
  66.             return self.getSize(directory)
  67.     def getThreshold(self, directory):
  68.         value = self.get('Threshold', type="integer", group=directory)
  69.         if value or value == 0:
  70.             return value
  71.         else:
  72.             return 2
  73.  
  74.     # validation stuff
  75.     def checkExtras(self):
  76.         # header
  77.         if self.defaultGroup == "KDE Icon Theme":
  78.             self.warnings.append('[KDE Icon Theme]-Header is deprecated')
  79.  
  80.         # file extension
  81.         if self.fileExtension == ".theme":
  82.             pass
  83.         elif self.fileExtension == ".desktop":
  84.             self.warnings.append('.desktop fileExtension is deprecated')
  85.         else:
  86.             self.warnings.append('Unknown File extension')
  87.  
  88.         # Check required keys
  89.         # Name
  90.         try:
  91.             self.name = self.content[self.defaultGroup]["Name"]
  92.         except KeyError:
  93.             self.errors.append("Key 'Name' is missing")
  94.  
  95.         # Comment
  96.         try:
  97.             self.comment = self.content[self.defaultGroup]["Comment"]
  98.         except KeyError:
  99.             self.errors.append("Key 'Comment' is missing")
  100.  
  101.         # Directories
  102.         try:
  103.             self.directories = self.content[self.defaultGroup]["Directories"]
  104.         except KeyError:
  105.             self.errors.append("Key 'Directories' is missing")
  106.  
  107.     def checkGroup(self, group):
  108.         # check if group header is valid
  109.         if group == self.defaultGroup:
  110.             pass
  111.         elif group in self.getDirectories():
  112.             try:
  113.                 self.type = self.content[group]["Type"]
  114.             except KeyError:
  115.                 self.type = "Threshold"
  116.             try:
  117.                 self.name = self.content[group]["Name"]
  118.             except KeyError:
  119.                 self.errors.append("Key 'Name' in Group '%s' is missing" % group)
  120.         elif not (re.match("^\[X-", group) and group.decode("utf-8", "ignore").encode("ascii", 'ignore') == group):
  121.             self.errors.append("Invalid Group name: %s" % group)
  122.  
  123.     def checkKey(self, key, value, group):
  124.         # standard keys        
  125.         if group == self.defaultGroup:
  126.             if re.match("^Name"+xdg.Locale.regex+"$", key):
  127.                 pass
  128.             elif re.match("^Comment"+xdg.Locale.regex+"$", key):
  129.                 pass
  130.             elif key == "Inherits":
  131.                 self.checkValue(key, value, list=True)
  132.             elif key == "Directories":
  133.                 self.checkValue(key, value, list=True)
  134.             elif key == "Hidden":
  135.                 self.checkValue(key, value, type="boolean")
  136.             elif key == "Example":
  137.                 self.checkValue(key, value)
  138.             elif re.match("^X-[a-zA-Z0-9-]+", key):
  139.                 pass
  140.             else:
  141.                 self.errors.append("Invalid key: %s" % key)
  142.         elif group in self.getDirectories():
  143.             if key == "Size":
  144.                 self.checkValue(key, value, type="integer")
  145.             elif key == "Context":
  146.                 self.checkValue(key, value)
  147.             elif key == "Type":
  148.                 self.checkValue(key, value)
  149.                 if value not in ["Fixed", "Scalable", "Threshold"]:
  150.                     self.errors.append("Key 'Type' must be one out of 'Fixed','Scalable','Threshold', but is %s" % value)
  151.             elif key == "MaxSize":
  152.                 self.checkValue(key, value, type="integer")
  153.                 if self.type != "Scalable":
  154.                     self.errors.append("Key 'MaxSize' give, but Type is %s" % self.type)
  155.             elif key == "MinSize":
  156.                 self.checkValue(key, value, type="integer")
  157.                 if self.type != "Scalable":
  158.                     self.errors.append("Key 'MinSize' give, but Type is %s" % self.type)
  159.             elif key == "Threshold":
  160.                 self.checkValue(key, value, type="integer")
  161.                 if self.type != "Threshold":
  162.                     self.errors.append("Key 'Threshold' give, but Type is %s" % self.type)
  163.             elif re.match("^X-[a-zA-Z0-9-]+", key):
  164.                 pass
  165.             else:
  166.                 self.errors.append("Invalid key: %s" % key)
  167.  
  168.  
  169. class IconData(IniFile):
  170.     "Class to parse and validate IconData Files"
  171.     def __init__(self):
  172.         IniFile.__init__(self)
  173.  
  174.     def __repr__(self):
  175.         return self.getDisplayName()
  176.  
  177.     def parse(self, file):
  178.         IniFile.parse(self, file, ["Icon Data"])
  179.  
  180.     # Standard Keys
  181.     def getDisplayName(self):
  182.         return self.get('DisplayName', locale=True)
  183.     def getEmbeddedTextRectangle(self):
  184.         return self.get('EmbeddedTextRectangle', list=True)
  185.     def getAttachPoints(self):
  186.         return self.get('AttachPoints', type="point", list=True)
  187.  
  188.     # validation stuff
  189.     def checkExtras(self):
  190.         # file extension
  191.         if self.fileExtension != ".icon":
  192.             self.warnings.append('Unknown File extension')
  193.  
  194.     def checkGroup(self, group):
  195.         # check if group header is valid
  196.         if not (group == self.defaultGroup \
  197.         or (re.match("^\[X-", group) and group.encode("ascii", 'ignore') == group)):
  198.             self.errors.append("Invalid Group name: %s" % group.encode("ascii", "replace"))
  199.  
  200.     def checkKey(self, key, value, group):
  201.         # standard keys        
  202.         if re.match("^DisplayName"+xdg.Locale.regex+"$", key):
  203.             pass
  204.         elif key == "EmbeddedTextRectangle":
  205.             self.checkValue(key, value, type="integer", list=True)
  206.         elif key == "AttachPoints":
  207.             self.checkValue(key, value, type="point", list=True)
  208.         elif re.match("^X-[a-zA-Z0-9-]+", key):
  209.             pass
  210.         else:
  211.             self.errors.append("Invalid key: %s" % key)
  212.  
  213.  
  214.  
  215. icondirs = []
  216. for basedir in xdg_data_dirs:
  217.     icondirs.append(os.path.join(basedir, "icons"))
  218. icondirs.append("/usr/share/pixmaps")
  219. icondirs.append(os.path.expanduser("~/.icons"))
  220.  
  221. # just cache variables, they give a 10x speed improvement
  222. themes = []
  223. cache = dict()
  224. dache = dict()
  225. eache = dict()
  226.  
  227. def getIconPath(iconname, size = None, theme = None, extensions = ["png", "svg", "xpm"]):
  228.     global themes
  229.  
  230.     if size == None:
  231.         size = xdg.Config.icon_size
  232.     if theme == None:
  233.         theme = xdg.Config.icon_theme
  234.  
  235.     # if we have an absolute path, just return it
  236.     if os.path.isabs(iconname):
  237.         return iconname
  238.  
  239.     # check if it has an extension and strip it
  240.     if os.path.splitext(iconname)[1][1:] in extensions:
  241.         iconname = os.path.splitext(iconname)[0]
  242.  
  243.     # parse theme files
  244.     try:
  245.         if themes[0].name != theme:
  246.             themes = []
  247.             __addTheme(theme)
  248.     except IndexError:
  249.         __addTheme(theme)
  250.  
  251.     # more caching (icon looked up in the last 5 seconds?)
  252.     tmp = "".join([iconname, str(size), theme, "".join(extensions)])
  253.     if eache.has_key(tmp):
  254.         if int(time.time() - eache[tmp][0]) >= xdg.Config.cache_time:
  255.             del eache[tmp]
  256.         else:
  257.             return eache[tmp][1]
  258.  
  259.     for thme in themes:
  260.         icon = LookupIcon(iconname, size, thme, extensions)
  261.         if icon:
  262.             eache[tmp] = [time.time(), icon]
  263.             return icon
  264.  
  265.     # cache stuff again (directories lookuped up in the last 5 seconds?)
  266.     for directory in icondirs:
  267.         if (not dache.has_key(directory) \
  268.             or (int(time.time() - dache[directory][1]) >= xdg.Config.cache_time \
  269.             and dache[directory][2] < os.path.getmtime(directory))) \
  270.             and os.path.isdir(directory):
  271.             dache[directory] = [os.listdir(directory), time.time(), os.path.getmtime(directory)]
  272.  
  273.     for dir, values in dache.items():
  274.         for extension in extensions:
  275.             try:
  276.                 if iconname + "." + extension in values[0]:
  277.                     icon = os.path.join(dir, iconname + "." + extension)
  278.                     eache[tmp] = [time.time(), icon]
  279.                     return icon
  280.             except UnicodeDecodeError:
  281.                 pass
  282.  
  283.     # we haven't found anything? "hicolor" is our fallback
  284.     if theme != "hicolor":
  285.         icon = getIconPath(iconname, size, "hicolor")
  286.         eache[tmp] = [time.time(), icon]
  287.         return icon
  288.  
  289. def getIconData(path):
  290.     if os.path.isfile(path):
  291.         dirname = os.path.dirname(path)
  292.         basename = os.path.basename(path)
  293.         if os.path.isfile(os.path.join(dirname, basename + ".icon")):
  294.             data = IconData()
  295.             data.parse(os.path.join(dirname, basename + ".icon"))
  296.             return data
  297.  
  298. def __addTheme(theme):
  299.     for dir in icondirs:
  300.         if os.path.isfile(os.path.join(dir, theme, "index.theme")):
  301.             __parseTheme(os.path.join(dir,theme, "index.theme"))
  302.             break
  303.         elif os.path.isfile(os.path.join(dir, theme, "index.desktop")):
  304.             __parseTheme(os.path.join(dir,theme, "index.desktop"))
  305.             break
  306.     else:
  307.         if debug:
  308.             raise NoThemeError(theme)
  309.  
  310. def __parseTheme(file):
  311.     theme = IconTheme()
  312.     theme.parse(file)
  313.     themes.append(theme)
  314.     for subtheme in theme.getInherits():
  315.         __addTheme(subtheme)
  316.  
  317. def LookupIcon(iconname, size, theme, extensions):
  318.     # look for the cache
  319.     if not cache.has_key(theme.name):
  320.         cache[theme.name] = []
  321.         cache[theme.name].append(time.time() - (xdg.Config.cache_time + 1)) # [0] last time of lookup
  322.         cache[theme.name].append(0)               # [1] mtime
  323.         cache[theme.name].append(dict())          # [2] dir: [subdir, [items]]
  324.  
  325.     # cache stuff (directory lookuped up the in the last 5 seconds?)
  326.     if int(time.time() - cache[theme.name][0]) >= xdg.Config.cache_time:
  327.         cache[theme.name][0] = time.time()
  328.         for subdir in theme.getDirectories():
  329.             for directory in icondirs:
  330.                 dir = os.path.join(directory,theme.name,subdir)
  331.                 if (not cache[theme.name][2].has_key(dir) \
  332.                 or cache[theme.name][1] < os.path.getmtime(os.path.join(directory,theme.name))) \
  333.                 and subdir != "" \
  334.                 and os.path.isdir(dir):
  335.                     cache[theme.name][2][dir] = [subdir, os.listdir(dir)]
  336.                     cache[theme.name][1] = os.path.getmtime(os.path.join(directory,theme.name))
  337.  
  338.     for dir, values in cache[theme.name][2].items():
  339.         if DirectoryMatchesSize(values[0], size, theme):
  340.             for extension in extensions:
  341.                 if iconname + "." + extension in values[1]:
  342.                     return os.path.join(dir, iconname + "." + extension)
  343.  
  344.     minimal_size = sys.maxint
  345.     closest_filename = ""
  346.     for dir, values in cache[theme.name][2].items():
  347.         distance = DirectorySizeDistance(values[0], size, theme)
  348.         if distance < minimal_size:
  349.             for extension in extensions:
  350.                 if iconname + "." + extension in values[1]:
  351.                     closest_filename = os.path.join(dir, iconname + "." + extension)
  352.                     minimal_size = distance
  353.  
  354.     return closest_filename
  355.  
  356. def DirectoryMatchesSize(subdir, iconsize, theme):
  357.     Type = theme.getType(subdir)
  358.     Size = theme.getSize(subdir)
  359.     Threshold = theme.getThreshold(subdir)
  360.     MinSize = theme.getMinSize(subdir)
  361.     MaxSize = theme.getMaxSize(subdir)
  362.     if Type == "Fixed":
  363.         return Size == iconsize
  364.     elif Type == "Scaleable":
  365.         return MinSize <= iconsize <= MaxSize
  366.     elif Type == "Threshold":
  367.         return Size - Threshold <= iconsize <= Size + Threshold
  368.  
  369. def DirectorySizeDistance(subdir, iconsize, theme):
  370.     Type = theme.getType(subdir)
  371.     Size = theme.getSize(subdir)
  372.     Threshold = theme.getThreshold(subdir)
  373.     MinSize = theme.getMinSize(subdir)
  374.     MaxSize = theme.getMaxSize(subdir)
  375.     if Type == "Fixed":
  376.         return abs(Size - iconsize)
  377.     elif Type == "Scalable":
  378.         if iconsize < MinSize:
  379.             return MinSize - iconsize
  380.         elif iconsize > MaxSize:
  381.             return MaxSize - iconsize
  382.         return 0
  383.     elif Type == "Threshold":
  384.         if iconsize < Size - Threshold:
  385.             return MinSize - iconsize
  386.         elif iconsize > Size + Threshold:
  387.             return iconsize - MaxSize
  388.         return 0
  389.